#!/usr/bin/env python

# PyOpenSG is (C) Copyright 2005-2009 by Allen Bierbaum
#
# This file is part of PyOpenSG.
#
# PyOpenSG is free software; you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# PyOpenSG is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
# more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Py++ script for PyOpenSG.
#
import distutils.sysconfig
import logging
import optparse
import os
import sys
import time

import pygccxml.declarations as pd

#import goodies.goodie_perf_overrides
from goodies import *

import osggen
import settings

pj = os.path.join

# TODO:
# - Implement finalize  (needs testing)
# - Ensure objects are stored as RecPtrs and auto converted to RecPtrs
# - Wrap begin/end edit
#
timing = [("start",time.time())]
use_profiler = False
use_query_opt = True

# Setup global settings
set_recursive_default(False)
sys.setrecursionlimit( sys.getrecursionlimit() * 10)   # Need a little more room for bindings
set_logger_level(logging.INFO)
#set_logger_level(logging.DEBUG)
import pyplusplus.code_creators.calldef
pyplusplus.code_creators.calldef.use_enum_workaround = True
import pyplusplus.function_transformers as FT


# ------------ Data output and generation --------------- #
# Idea: Generate this from doxygen or opensg fcd files

gen_classes = []

# Add basic classes that can just be wrapped with no customization
for c in ["ActionBase", "SimpleSceneManager", "Navigator", "RenderPartitionBase", "Connection",
          "PointConnection", "GroupConnection", "ConnectionType", "ClusterNetwork",
          "ClusterServer", "TreeBuilderBase"]:
   gen_classes.append(osggen.ClassInfo(c))

# For OSG::ExceptionBinaryDataHandler::petValues(), we could set things up so that the input is a
# Python list. From there, however, it becomes tricky because we need to know the data type in the
# list in order to get the correct overload of OSG::ExceptionBinaryDataHandler::petValues().
gen_classes.append(
   osggen.ClassInfo("ExceptionBinaryDataHandler",
                    excludeList = ["put", "putAndFree", "putValues", "get",
                                   "getAndAlloc", "getValues"])
)

# Exclude begin, beginCreated, end, endCreated because they return non-portable iterators.
gen_classes.append(
   osggen.ClassInfo("ChangeList",
                    excludeList = ['begin', 'beginCreated', 'end', 'endCreated'],
                    policyList = [("create", return_value_policy(reference_existing_object))])
)

action_callback_decl_code = """
namespace
{

OSG::ActionBase::ResultE invokeCallback(bp::object obj,
                                        OSG::NodeCore* const core,
                                        OSG::Action* action)
{
   OSG::NodeCoreRecPtr core_ptr(core);
   return bp::extract<OSG::ActionBase::ResultE>(obj(core_ptr, action));
}

}
"""

action_static_decl_code = """
namespace
{

void wrapRegisterEnterDefault(const OSG::FieldContainerType& type,
                              bp::object func)
{
   OSG::%(Action)s::registerEnterDefault(type, boost::bind(invokeCallback, func, _1, _2));
}

void wrapRegisterLeaveDefault(const OSG::FieldContainerType& type,
                              bp::object func)
{
   OSG::%(Action)s::registerLeaveDefault(type, boost::bind(invokeCallback, func, _1, _2));
}

}
"""

action_mem_decl_code = """
namespace
{

void wrapRegisterEnterFunction(OSG::Action* self,
                               const OSG::FieldContainerType& type,
                               bp::object func)
{
   self->registerEnterFunction(type,
                               boost::bind(invokeCallback, func, _1, _2));
}

void wrapRegisterLeaveFunction(OSG::Action* self,
                               const OSG::FieldContainerType& type,
                               bp::object func)
{
   self->registerLeaveFunction(type,
                               boost::bind(invokeCallback, func, _1, _2));
}

}
"""

action_subclass_mem_decl_code = """
namespace
{

OSG::ActionBase::ResultE wrapApply(OSG::%(class_name)s* self, bp::list nodes)
{
   const int num_nodes(bp::extract<int>(nodes.attr("__len__")()));
   std::vector<OSG::Node*> node_vec(num_nodes);

   for ( int i = 0; i < num_nodes; ++i )
   {
      OSG::Node::ObjRecPtr n = bp::extract<OSG::Node::ObjRecPtr>(nodes[i]);
      node_vec[i] = n.get();
   }

   return self->apply(node_vec.begin(), node_vec.end());
}

}
"""

action_static_reg_code = [
   'def("registerEnterDefault", wrapRegisterEnterDefault, (bp::arg("type"), bp::arg("func")))',
   'def("registerLeaveDefault", wrapRegisterLeaveDefault, (bp::arg("type"), bp::arg("func")))',
]

action_mem_reg_code = [
   'def("registerEnterFunction", wrapRegisterEnterFunction, (bp::arg("type"), bp::arg("func")))',
   'def("registerLeaveFunction", wrapRegisterLeaveFunction, (bp::arg("type"), bp::arg("func")))',
]

# We will keep the single-argument inherited overload of OSG::Action::apply(). For the
# two-argument overload of OSG::Action::apply(), we exclude the regular virtual version. For
# now, I do not see a need for a Python subclass to be able to customize OSG::IntersectAction
# or OSG::RenderAction. If that changes, then something similar to what is done for the
# two-argument OSG::Action::apply() overload can be applied to these classes.
action_wrapApply_reg_code = [
   'def("apply", wrapApply, (bp::arg("nodes")))',
]
action_subclass_mem_reg_code = [
   action_wrapApply_reg_code[0],
   """
   def("apply",
       (OSG::ActionBase::ResultE (OSG::Action::*)(OSG::Node* const)) &OSG::Action::apply,
       (bp::arg("node")))
   """,
]

# OSG::Action can be customized by a Python subclass.
gen_classes.append(
   osggen.ClassInfo(
      "Action",
      excludeList = ["registerEnterDefault", "registerEnterFunction",
                     "registerLeaveDefault", "registerLeaveFunction"],
      declarationCode = [action_callback_decl_code,
                         action_static_decl_code % {'Action' : 'Action'},
                         action_mem_decl_code,
                         action_subclass_mem_decl_code % {'class_name' : 'Action'}],
      registrationCode = action_static_reg_code + action_mem_reg_code + action_wrapApply_reg_code,
      useSharedPtr = False
   )
)

# OSG::RenderActionBase cannot be customized by a Python subclass.
gen_classes.append(osggen.ClassInfo("RenderActionBase", finalize = True))

# OSG::RenderAction cannot be customized by a Python subclass.
gen_classes.append(
   osggen.ClassInfo(
      "RenderAction", 
      excludeList = ["dropFunctor", "registerEnterDefault", "registerLeaveDefault"],
      declarationCode = [action_callback_decl_code,
                         action_static_decl_code % {'Action' : 'RenderAction'},
                         action_subclass_mem_decl_code % {'class_name' : 'RenderAction'}],
      registrationCode = action_static_reg_code + action_subclass_mem_reg_code,
      finalize = True
   )
)

# OSG::IntersectAction cannot be customized by a Python subclass.
gen_classes.append(
   osggen.ClassInfo(
      "IntersectAction",
      excludeList = ["registerEnterDefault", "registerLeaveDefault"],
      declarationCode = [action_callback_decl_code,
                         action_static_decl_code % {'Action' : 'IntersectAction'},
                         action_subclass_mem_decl_code % {'class_name' : 'IntersectAction'}],
      registrationCode = action_static_reg_code + action_subclass_mem_reg_code,
      finalize = True
   )
)

gen_classes.append(osggen.ClassInfo("RenderPartition", excludeList = ["dropFunctor"]))

# Exclude OSG::SceneFileType::read() and OSG::SceneFileType::write() because they require a C++
# stream object as an argument. We could probably devise a wrapper that maps between a Python list
# and a C++ stream, though.
gen_classes.append(osggen.ClassInfo("SceneFileType",
                                    excludeList = ["read", "write", "suffixList"]))

def statCollectorCustomizations(klass):
   """ Remove all the operators. """
   cmethods = klass.calldefs()
   for m in cmethods:
      if isinstance(m, operator_t) and m.symbol in ["+", "+="]:
         m.exclude()   

gen_classes.append(
   osggen.ClassInfo("StatCollector",
                    excludeList = ["StatCollector", "getElem", "reset", "getValue"],
                    policyList = [("create", return_value_policy(reference_existing_object))],
                    modCallable = statCollectorCustomizations)
)

ra_decl_code = """
namespace
{

bool invokeCallback(bp::object obj, const OSG::FieldContainer* fc,
                    OSG::RemoteAspect* ra)
{
   return bp::extract<bool>(obj(fc, ra));
}

void wrapRegisterCreated(OSG::RemoteAspect* self,
                         const OSG::FieldContainerType& type, bp::object func)
{
   self->registerCreated(type, boost::bind(invokeCallback, func, _1, _2));
}

void wrapRegisterDestroyed(OSG::RemoteAspect* self,
                           const OSG::FieldContainerType& type,
                           bp::object func)
{
   self->registerDestroyed(type, boost::bind(invokeCallback, func, _1, _2));
}

void wrapRegisterChanged(OSG::RemoteAspect* self,
                         const OSG::FieldContainerType& type, bp::object func)
{
   self->registerChanged(type, boost::bind(invokeCallback, func, _1, _2));
}

}
"""

ra_reg_code = [
   'def("registerCreated", wrapRegisterCreated, (bp::arg("type"), bp::arg("func")))',
   'def("registerDestroyed", wrapRegisterDestroyed, (bp::arg("type"), bp::arg("func")))',
   'def("registerChanged", wrapRegisterChanged, (bp::arg("type"), bp::arg("func")))',
]

gen_classes.append(
   osggen.ClassInfo("RemoteAspect",
                    excludeList = ["registerCreated", "registerDestroyed", "registerChanged"],
                    declarationCode = [ra_decl_code],
                    registrationCode = ra_reg_code)
)

# OSG::Thread is exposed so that we can get access to the change list for a given thread, not so
# that Python programmers can spawn OpenSG threads. If it is deemed necessary that Python
# programmers be able to do this, then some further special consideration will probably be needed
# for this class. In particular, OGS::Thread::runFunction() would need to handle accepting a
# Python callable. This may not be so easy, however, as OSG::Thread does not use an instance of
# boost::function<T> as the thread callback type.
gen_classes.append(
   osggen.ClassInfo(
      "Thread",
      excludeList = ["runFunction"],
      policyList = [("find", return_value_policy(reference_existing_object)),
                    ("getCurrentChangeList", return_value_policy(reference_existing_object))]
   )
)

gen_classes.append(osggen.ClassInfo("RenderTreeNode", excludeList = ["getFunctor", "setFunctor"]))

# Instead of excluding begin() and end() for OSG::StateOverride's ChunkStore, it would be nice to
# have a property such as "chunks" that returns an iterator.
gen_classes.append(osggen.ClassInfo("StateOverride", excludeList = ["begin", "end"]))

# --- Node Cores and other subclasses of OSG::FieldContainer ------ #

# Map from class name to osggen.FCInfo class
fc_infos = {}

# fc's that are custom (not in fcd files)
fc_infos["AttachmentContainer"] = osggen.FCInfo("AttachmentContainer", "FieldContainer",
                                                hasBase = False,
                                                excludeList = ["shallowCopy", "shallowCopyLocal",
                                                               "createAspectCopy"])

fc_infos["Attachment"] = osggen.FCInfo("Attachment", "AttachmentContainer", hasBase = False,
                                       excludeList = ["shallowCopy", "shallowCopyLocal",
                                                      "createAspectCopy"])

# Base class for state chunks and other things
#fc_infos["FieldContainerAttachment"] = osggen.FCInfo("FieldContainerAttachment",
#                                                     "FieldContainer", hasBase = False)

fc_infos["Node"] = osggen.FCInfo("Node", "AttachmentContainer", hasBase = False)

fc_infos["NodeCore"] = osggen.FCInfo("NodeCore", "AttachmentContainer", hasBase = False)

fc_infos["ChunkMaterial"] = \
   osggen.FCInfo("ChunkMaterial", "Material",
                 policyList = [("getChunk", return_value_policy(reference_existing_object))])

# OSG::CameraDecorator is a rare field container type that does not have a .fcd file.
fc_infos["CameraDecorator"] = osggen.FCInfo("CameraDecorator", "Camera")

# Field containers that need customization.
fc_infos["Image"] = osggen.FCInfo("Image", "AttachmentContainer",
                                  ["store", "restore", "getData", "getDataByTime", "getDataFast",
                                   "editDataByTime", "calcMipmapGeometry", "clearHalf"])

fc_infos["Window"] = osggen.FCInfo("Window","AttachmentContainer",
                                   ["getFunction", "getFunctionNoCheck", "getFunctionByName",
                                    "destroyGLObject", "getGLObjectsSize", "refreshAllGLObjects",
                                    "refreshGLObject", "registerGLObject",
                                    "reinitializeAllGLObjects", "reinitializeGLObject",
                                    "registerExtension", "registerFunction"])

#fc_infos["GeoProperty"] = CoreInfo("GeoProperty", "StateChunk", ["getData"])

# Build up list of template methods that we need to instantiate so we can get all of them exposed.
gvp_tmpl_instances = \
   ["template %s OSG::GeoVectorProperty::getValue<%s>(const OSG::UInt32) const" % \
       (t, t) for t in settings.geo_vec_get_value_ret_types]
gvp_tmpl_instances.extend(
   ["template void OSG::GeoVectorProperty::setValue<%s>(const %s&, const OSG::UInt32)" % \
       (t, t) for t in settings.geo_vec_get_value_ret_types]
)
gvp_tmpl_instances.extend(
   ["template void OSG::GeoVectorProperty::addValue<%s>(const %s&)" % \
       (t, t) for t in settings.geo_vec_get_value_ret_types]
)
gvp_tmpl_instances.extend(
   ["template void OSG::GeoVectorProperty::push_back<%s>(const %s&)" % \
       (t, t) for t in settings.geo_vec_get_value_ret_types]
)
                          
# Expose the GeoVectorProperty class
# - The getValue stuff will trigger code that renames the overloaded template methods to use names
#   sach as getValue_Color3f.
# - The tmpl_instances are used to make sure that the compiler sees all the versions of these
#   overloaded methods that we need.
gvp = osggen.FCInfo("GeoVectorProperty", "GeoProperty", templateMethods = ["getValue"],
                    templateInstances = gvp_tmpl_instances)
fc_infos[gvp.name] = gvp

# Build up list of template methods that we need to instantiate so we can get all of them exposed.
gip_tmpl_instances = \
   ["template OSG::%s OSG::GeoIntegralProperty::getValue<OSG::%s>(const OSG::UInt32) const" % \
       (t, t) for t in settings.geo_integral_types]
gip_tmpl_instances.extend(
   ["template void OSG::GeoIntegralProperty::setValue<OSG::%s>(const OSG::%s&, const OSG::UInt32)" % \
      (t, t) for t in settings.geo_integral_param_types]
)
gip_tmpl_instances.extend(
   ["template void OSG::GeoIntegralProperty::addValue<OSG::%s>(const OSG::%s&)" % \
       (t, t) for t in settings.geo_integral_param_types]
)
gip_tmpl_instances.extend(
   ["template void OSG::GeoIntegralProperty::push_back<OSG::%s>(const OSG::%s&)" % \
       (t, t) for t in settings.geo_integral_param_types]
)

# Expose all the real versions we want


# Expose the GeoIntegralProperty class
# - The getValue stuff will trigger code that renames the overloaded template methods to use names
#   sach as getValue_Color3f.
# - The tmpl_instances are used to make sure that the compiler sees all the versions of these
#   overloaded methods that we need.
gip = osggen.FCInfo("GeoIntegralProperty", "GeoProperty",
                    templateMethods = ["getValue"], templateInstances = gip_tmpl_instances)
fc_infos[gip.name] = gip

# Build a list of pairs where the first element in the pair is the derived class name of interest
# and the second element is the field container base class name. These are classes that have
# member templates for accessing shader uniforms.
shader_obj_types = [("SimpleSHLChunk", "ShaderProgramChunk"),
                    ("SimpleSHLVariableChunk", "StateChunk"),
                    ("ShaderProgramVariableChunk", "StateChunk"),
                    ("ShaderProgram", "FieldContainer")]

# Build up list of template methods that we need to instantiate so we can get all of them exposed.
for shader_obj_type, shader_base_type in shader_obj_types:
   tmpl_instances = \
      ["template bool OSG::%s::addUniformVariable(const OSG::Char8*, const %s&)" % \
          (shader_obj_type, t) for t in settings.shl_variable_types]
   tmpl_instances.extend(
      ["template bool OSG::%s::updateUniformVariable(const OSG::Char8*, const %s&)" % \
          (shader_obj_type, t) for t in settings.shl_variable_types]
   )
   # Handling for exposing this method to Python is done later on in this script.
   tmpl_instances.extend(
      ["template bool OSG::%s::getUniformVariable(const OSG::Char8*, %s&)" % \
          (shader_obj_type, t) for t in settings.shl_variable_types]
   )

   fc_infos[shader_obj_type] = osggen.FCInfo(shader_obj_type, shader_base_type,
                                             templateInstances = tmpl_instances)

def modGeoVectorProperty(cls):
   pass
   # Exclude get values that return by ref
   #cls.member_functions("getValue", arg_types = [None, None]).exclude()
   
   # Exclude default setValue
   #cls.member_functions(
   #   calldef_matcher("setValue") & virtuality_type_matcher(VIRTUALITY_TYPES.PURE_VIRTUAL)
   #).exclude()
   
# Map of core id to callables to call on (class, base) decl
core_mods = {}
core_mods["Slices"] = [ [lambda c: c["Edge"].exclude(), 
                         lambda c: c["Slice"].exclude(),
                         lambda c: c["createSlice"].exclude(),
                         lambda c: c["drawSlices"].exclude()],
                        [] ]
#core_mods["Image"] = [ [lambda c: c["createMipmap"].use_overload_macro = True, ], []]
core_mods[gvp.name] = [ [modGeoVectorProperty,],[]]

# ---- Free functions ----- #

free_funcs = ["calcVertexNormals", "calcVertexNormalsGeo", "calcFaceNormalsGeo",
              "getDefaultMaterial", "getDefaultUnlitMaterial",
              "makePlane", "makePlaneGeo",
              "makeCone", "makeConeGeo",
              "makeCylinder", "makeCylinderGeo",
              "makeConicalFrustum", "makeConicalFrustumGeo",
              "makeTorus", "makeTorusGeo",
              "makeSphere", "makeSphereGeo",
              "makeLatLongSphere", "makeLatLongSphereGeo",
              "makeBox", "makeBoxGeo",
              "makeCoordAxis", "makeCoordAxisGeo",
              "makeTeapot", "makeTeapotGeo",
              "setName", "getName", "stringAttributeMap",
              "cloneTree", "deepCloneTree",
              "commitChanges"]

# Map of public method names to callables to call on the methods decls
free_func_mods = {}

def setUseOverloadMacro(m):
   m.use_overload_macro = True

# gccxml bug causes problems with default arguments on these methods.
for m in ["cloneTree", "deepCloneTree"]:
   free_func_mods[m] = [setUseOverloadMacro]

# ----------------------------------------------------------- #
#            MAIN CODE GENERATION                             #
# ----------------------------------------------------------- #
def main():
   global timing
   import getpass
   username = getpass.getuser()

   # --- Collect all settings for build ---- #
   opensg_inc_dir = os.path.join(os.popen('osg2-config --prefix', 'r').readline().strip(),
                                 'include')
   opensg_cflags = os.popen('osg2-config --cflags System Base', 'r').readline().strip()
   opensg_cflags = opensg_cflags.replace("-use_readonly_const", "")
   boost_cflags = "-DBOOST_PYTHON_MAX_ARITY=21"

   options, args  = parseOptions(opensg_inc_dir)
   opensg_src_dir = options.opensg_src_dir
   boost_inc_dir  = options.boost_inc_dir

   if opensg_src_dir is None:
      print "ERROR: No path to OpenSG's Source directory provided!"
      sys.exit(1)
   if boost_inc_dir is None:
      print "ERROR: No path to Boost header directory provided!"
      sys.exit(2)

   python_inc_dir = distutils.sysconfig.get_python_inc()
   output_dir = "osg_module"
   main_header_filename = os.path.join(output_dir,'osgmodule_mainheader.h')
   defines = []              
   gccxml_path = ""
   inc_path = [output_dir, opensg_inc_dir, os.path.join(opensg_inc_dir, "OpenSG"), 
               boost_inc_dir, python_inc_dir]
   
   # --- Search for .fcd files and build up class info ------- #
   subdirs = ["Base", "System", "Contrib/ComplexSceneManager", "WindowSystem/GLUT"]

   fc_ignore_list = ["ImageComposer", "DisplayCalibration", "SortFirstWindow", "SortLastWindow",
                     "SepiaComposer", "BinarySwapComposer", "PipelineComposer",
                     "ParallelComposer", "BalancedMultiWindow", "DisplayFilterForeground",
                     "ShadowMapEngine", "SimpleShadowMapEngine"]

   # .fcd files to skip because they only cause trouble.
   skip_fcds = [
      os.path.join(opensg_src_dir, "System", "State", "SHL", "Parameter", "OSGShaderParameter.fcd"),
      os.path.join(opensg_src_dir, "Contrib", "ComplexSceneManager", "Sensor", "InterfaceSensors",
                   "Devices", "OSGLinuxEventOptions.fcd"),
      os.path.join(opensg_src_dir, "System", "FileIO", "Collada",
                   "OSGColladaGeoInputAttachment.fcd")
   ]

   osggen.findFieldContainerDescriptors(fc_infos, opensg_src_dir, subdirs,
                                        os.path.join(opensg_inc_dir, "OpenSG"), fc_ignore_list,
                                        skip_fcds)
   print "Found %d field container types" % len(fc_infos)

   # ------ Manually define headers files -------- #
   # These includes are added later to the module builder
   extra_includes = ["OsgPtrHelpers.h",
                     "boost/python/suite/indexing/map_indexing_suite.hpp",
                     "boost/python/suite/indexing/vector_indexing_suite.hpp"]
   
   all_headers = []
   
   fundamental_headers = ["PreBoostPython.h"]

   type_headers = ["OpenSG/OSGTypeBase.h",
                 "OpenSG/OSGMemoryObject.h",
                 "OpenSG/OSGDataType.h",
                 'OpenSG/OSGFieldType.h',
                 'OpenSG/OSGBaseTypes.h',
                 'OpenSG/OSGField.h',
                 'OpenSG/OSGFieldType.h',
                 #'OpenSG/OSGFieldFactory.h',
                 'OpenSG/OSGFieldDescriptionBase.h',
                 'OpenSG/OSGFieldContainerType.h',
                 'OpenSG/OSGFieldContainerFactory.h',
                 'OpenSG/OSGFieldContainer.h',              
                 'OpenSG/OSGChangeList.h',              
                 'OpenSG/OSGStageParent.h',
                 ]
                 
   math_headers = ["OpenSG/OSGLine.h",
                 "OpenSG/OSGVolume.h",           
                 "OpenSG/OSGVector.h",
                 "OpenSG/OSGColor.h",
                 "OpenSG/OSGMatrix.h",
                 'OpenSG/OSGQuaternion.h',
                 'OpenSG/OSGPlane.h',
                 'OpenSG/OSGBoxVolume.h',
                 'OpenSG/OSGCylinderVolume.h',
                 'OpenSG/OSGFrustumVolume.h',
                 'OpenSG/OSGPolytopeVolume.h',
                 'OpenSG/OSGSphereVolume.h'                   
                 ]
                 
   free_func_headers = ['OpenSG/OSGStatCollector.h',
                 'OpenSG/OSGBaseFunctions.h',
                 "OpenSG/OSGImageFileHandler.h",               
                 "OpenSG/OSGImageFileType.h",               
                 "OpenSG/OSGSceneFileHandler.h",               
                 "OpenSG/OSGSceneFileType.h",               
                 "OpenSG/OSGGraphOpSeq.h",
                 "OpenSG/OSGSimpleGeometry.h",
                 "OpenSG/OSGGeoFunctions.h",
                 "OpenSG/OSGMatrixUtility.h",
                 "OpenSG/OSGVolumeDraw.h",
                 "OpenSG/OSGConnectionFactory.h",               
                 ]
   
   misc_headers = [#"OpenSG/OSGAttachmentPtr.h",
                   #'OpenSG/OSGAttachmentContainerPtr.h',
                   'OpenSG/OSGNode.h',
                   'OpenSG/OSGBinaryDataHandler.h',
                   #'OpenSG/OSGStringConversionStateBase.h',
                   'OpenSG/OSGNameAttachment.h',
                   #'OpenSG/OSGDrawAction.h',
                   'OpenSG/OSGEdgeIterator.h',
                   'OpenSG/OSGTriangleIterator.h',
                   'OpenSG/OSGPrimitiveIterator.h',
                   'OpenSG/OSGFaceIterator.h',
                   'OpenSG/OSGLineIterator.h',
                   'OpenSG/OSGState.h',
                   'OpenSG/OSGBackground.h',
                   'OpenSG/OSGLightEnv.h',
                   #'OpenSG/OSGBrick.h',
                   "OpenSG/OSGGeoTypeGraphOp.h",
                   "OpenSG/OSGGraphOpFactory.h",
                   "OpenSG/OSGGraphOp.h",
                   "OpenSG/OSGGraphOpSeq.h",
                   "OpenSG/OSGMakeTransparentGraphOp.h",
                   "OpenSG/OSGMaterialMergeGraphOp.h",
                   "OpenSG/OSGMergeGraphOp.h",
                   "OpenSG/OSGPruneGraphOp.h",
                   "OpenSG/OSGSharePtrGraphOp.h",
                   "OpenSG/OSGSingleTypeGraphOp.h",
                   "OpenSG/OSGSplitGraphOp.h",
                   "OpenSG/OSGStripeGraphOp.h",
                   "OpenSG/OSGVerifyGeoGraphOp.h",
                   "OpenSG/OSGVerifyGraphOp.h",
                   "OpenSG/OSGException.h",
                   "OpenSG/OSGClusterException.h",
                   "OpenSG/OSGExceptionBinaryDataHandler.h",
                   "OpenSG/OSGSocketException.h",
                   "OpenSG/OSGPerfMonitor.h",
                   "OpenSG/OSGWalkEngine.h",
                   "OpenSG/OSGTrackballEngine.h",
                   "OpenSG/OSGNoneEngine.h",
                   "OpenSG/OSGFlyEngine.h",
                   "OpenSG/OSGNavigatorEngine.h",
                   "OpenSG/OSGNavigator.h",
                   "OpenSG/OSGShaderCache.h",
                   ]

   wrapper_headers = ["ColorWrapper.h", 
                    "Wrappers.h",
                    "VecStorageWrappers.h",]
   workaround_headers = ["aliases.h"]

   core_headers = []
      
   for c in fc_infos.itervalues():
      core_headers.append("OpenSG/OSG%s.h"%c.name)
      #if c.parent:
      #   core_headers.append("OpenSG/OSG%sBase.h"%c.parent)   
      core_headers.extend(c.includes)
   
   for class_info in gen_classes:
      core_headers.append("OpenSG/OSG%s.h" % class_info.name)   
      core_headers.extend(class_info.includes)
      
   all_headers = fundamental_headers + type_headers + math_headers + free_func_headers + \
                    misc_headers
   all_headers.extend(wrapper_headers + workaround_headers + core_headers)
   
   all_headers_full_paths = []
   for f in all_headers:
      for p in inc_path:
         if os.path.isfile(pj(p,f)):
            all_headers_full_paths.append(pj(p,f))
            continue
   
   #all_headers = base_osg_headers + wrapper_headers
   
   #print "Core: ", core_headers
   
   print "Building PyOpenSG module...."
   print "setting up templates..."
   template_builder = osggen.LocalTemplateBuilder()
   tbuilder = TemplateBuilder()   
   
   # Add the math templates
   for vst in settings.vec_storage_typeinfo:
      template_builder.Template(vst.type, vst.alias)
   
   for svst in settings.select_vec_storage_typeinfo:
      template_builder.Template(svst.type, svst.alias)
   
   for vt in settings.vec_typeinfo:
      template_builder.Template(vt.type, vt.alias)
      
   for pt in settings.pnt_typeinfo:
      template_builder.Template(pt.type, pt.alias) 
   
   #pnt_templates = []
   #for size in settings.pnt_sizes:
   #   for type_str in settings.pnt_types.iteritems():
   #      pnt_temp = tb.Template("OSG::Pnt%s%s"%(size, type_str),
   #                             "Pnt%s%s"%(size,type_str))
      
   for ct in settings.color_typeinfo:
      template_builder.Template(ct.type, ct.alias) 
         
   
   mat_temp = tbuilder.Template("OSG::Matrix", "Matrix")
   matd_temp = tbuilder.Template("OSG::Matrix4d", "Matrix4d")
   quat_temp = tbuilder.Template("OSG::Quaternion","Quaternion")
   quatd_temp = tbuilder.Template("OSG::Quaterniond","Quaterniond")
   
   gof_temp = tbuilder.Template("OSG::GraphOpFactory", "GraphOpFactory")
   sfh_temp = tbuilder.Template("OSG::SceneFileHandler","SceneFileHandler")
   ifh_temp = tbuilder.Template("OSG::ImageFileHandler","ImageFileHandler")
   fcf_temp = tbuilder.Template("OSG::FieldContainerFactory","FieldContainerFactory")
   rtnp_temp = tbuilder.Template("OSG::RenderTreeNodePool",
                                 "RenderTreeNodePool")
   sop_temp = tbuilder.Template("OSG::StateOverridePool", "StateOverridePool")
   #tbp_temp = tbuilder.Template("OSG::TreeBuilderPool", "TreeBuilderPool")
   scp_temp = tbuilder.Template("OSG::ContainerMixinHead<OSG::StageDesc>",
                                "StageContainerParent")
   sp_temp = tbuilder.Template("OSG::StageParent", "StageParent")
   cf_temp = tbuilder.Template("OSG::ConnectionFactory", "ConnectionFactory")

   pm_temp = tbuilder.Template("OSG::PerfMonitor","PerfMonitor")

   stat_templates = []
   for x in ["StatIntElem", "StatTimeElem", "StatRealElem", "StatIntOnceElem"]:
      temp = tbuilder.Template("OSG::StatElemDesc<OSG::%s>" % x,
                               "StatElemDesc_%s" % x)
      stat_templates.append(temp)

   # Member template instantiations
   template_instance_text  = "\n\n// Member template instantiations\n"
   template_instance_text += "#if defined(__GCCXML__)\n"
   for c in fc_infos.itervalues():
      for t in c.templateInstances:
         inst_text = "%s;\n"%t
         template_instance_text = template_instance_text + inst_text
   template_instance_text += "#endif\n"

   # Specify manual template instantiations.
   forced_template_instances = []
   # XXX: Currently there is a bug (#5439) in gccxml that does all explicit member template
   #      instantiation of templated classes. For now we will have to manually wrap these methods.
   #
   #forced_template_instances.extend(
   #   ["template void OSG::Matrix4f::mult(const OSG::TransformationMatrix<%s>&)" % \
   #      (t, t) for t in ['OSG::Real32', 'OSG::Real64']]
   #)
   #forced_template_instances.extend(
   #   ["template void OSG::Matrix4d::mult(const OSG::TransformationMatrix<%s>&)" % \
   #      (t, t) for t in ['OSG::Real32', 'OSG::Real64']]
   #)
   #forced_template_instances.extend(
   #   ["template void OSG::Matrix4f::multLeft(const OSG::TransformationMatrix<%s>&)" % \
   #      (t, t) for t in ['OSG::Real32', 'OSG::Real64']]
   #)
   #forced_template_instances.extend(
   #   ["template void OSG::Matrix4d::multLeft(const OSG::TransformationMatrix<%s>&)" % \
   #      (t) for t in ['OSG::Real32', 'OSG::Real64']]
   #)

   # Add all manually specified template instantiations.
   #template_instance_text += "\n\n// Manual template instantiations\n"
   #template_instance_text += "#if defined(__GCCXML__)\n"
   #for t in forced_template_instances:
   #   template_instance_text += "%s;\n" % t
   #template_instance_text += "#endif\n"

   # Geo vector template types
   geo_vec_prop_templates = []
   for x in settings.geo_vec_prop_names:
      geo_vec_prop_templates.append(tbuilder.Template("OSG::%s"%x, x))
   geo_int_prop_templates = []
   for x in settings.geo_integral_prop_names:
      geo_int_prop_templates.append(tbuilder.Template("OSG::%s"%x, x))
   
   # Parse with builder
   print "  creating header file:", main_header_filename
   main_header_filename = \
      osggen.buildContainerHeader(
         all_headers, 
         template_builder.buildTemplateFileContents() + tbuilder.buildAutogenContents() + \
            template_instance_text,
         filename = main_header_filename
      )
   
   print "creating builder and parsing."
   cache_file = "pypp.pyopensg.cache"
   timing.append(["collect headers and templates",time.time()])
   
   mb = ModuleBuilder([main_header_filename],
                      working_directory = ".",
                      include_paths=inc_path,
                      cache=cache_file+".module",
                      define_symbols=defines,
                      ignore_gccxml_output=True,
                      optimize_queries=use_query_opt,
                      #start_with_declarations=["OSG",],
                      cflags=opensg_cflags + " " + boost_cflags)
                      #dependent_headers=all_headers_full_paths)
   tbuilder.processTemplates(mb)
   mb.BOOST_PYTHON_MAX_ARITY = 21
   mb.global_ns.exclude()
   osg = mb.global_ns.namespace("OSG", recursive=False)   
   
   timing.append(["module builder init",time.time()])
   
   if False:
      decl_file = file("decl_out_early.txt",'w', 1024000)
      print "Writing out decls to file... ",
      mb.print_declarations(osg, writer=decl_file.write)
      #mb.print_declarations(my_class)
      decl_file.close()
      print "done."
   
   #mb.print_declarations()
   #mb.print_declarations(my_class)
   
   # ------------ Helper methods and data -------------------- #
   
   # Map from template alias name to real decl wrapper type
   print "Building template alias map..."
   template_alias_db = {}
   osg_typedef_db = {}

   global_typedefs = mb.global_ns.typedefs()   
   for td in global_typedefs:
      template_alias_db[td.name] = td.type

   osg_typedefs = osg.typedefs()

   def getOsgTypedefFromType(_type, typedefs = osg_typedefs):
      """ Given a type, look up the original typedef. """
      type_name = _type.declaration.name

   #print "Typedefs: "
   #for (name, _type) in template_alias_db.iteritems():
   #   print "  %s: %s"%(name, _type) 

   #typedefs = osg.typedefs()
   #print "OSG Typedefs:"
   #for td in typedefs:
   #   print "%s -- %s -- %s"%(td.name, td.alias, td.type)
   
   #typedefs = mb.global_ns.typedefs()
   #print "Global Typedefs:"
   #for td in typedefs:
   #   print "%s -- %s -- %s"%(td.name, td.alias, td.type)
         
   
   print "# ---------------  Base Memory and Field Types ----------------- #"
   
   # Expose classes
   # - Memory/Type classes
   tb = osg["TypeBase"]
   tb.include()
   tb["GlobalNamespace"].exclude()
   osggen.handleClassDefaults(tb, True)
   tb["getParent"].call_policies = return_internal_reference()
   finalize(tb)

   mo = osg["MemoryObject"]
   mo.include()

   # Do not let Py++ think that it needs to generate a wrapper for
   # OSG::MemoryObject to expose the constructor and copy constructor for the
   # class.
   for c in mo.constructors():
      c.exclude()

   # Be sure that OSG::MemoryObject instances cannot be copied. The copy
   # constructor is protected in the declaration, and this is here just to be
   # sure that Py++ gets it right.
   for d in mo.declarations:
      d.noncopyable = True

   dt = osg["DataType"]
   dt.include()
   osggen.handleClassDefaults(dt, True)
   finalize(dt)
   
   # - Field classes
   ft = osg["FieldType"]
   ft.include()
   finalize(ft)
   
   #ff = decl_from_typedef( osg["FieldFactory"] )
   #ff.include()
   #ff["createField"].call_policies = return_value_policy(manage_new_object)
   #ff["getFieldType"].call_policies  = return_internal_reference()
   #ff["the"].call_policies = return_value_policy(reference_existing_object)
   #ff["_fieldTypeM"].exclude()
   #finalize(ff)
   
   fd = osg["FieldDescriptionBase"]
   fd.include()
   for x in ["_fEditMethod", "_fGetMethod", "_fIndexedGetMethod", "_fIndexedEditMethod"]:
      fd[x].exclude()
   for n in ["getFieldType"]:
      fd[n].call_policies = return_internal_reference()
   for n in ["createConnector", "createField", "clone"]:
      fd[n].call_policies = return_value_policy(manage_new_object)

   rct = osg.class_("ReflexiveContainerType")    #.finalize()   
   rct.include()
   osggen.handleClassDefaults(rct, False)

   fct = osg.class_("FieldContainerType")    #.finalize()   
   fct.include()
   osggen.handleClassDefaults(fct, True)
   finalize(fct)
   
   ##fct["getParent"].exclude()
   #fct["addDescription"].exclude()
   #fct["subDescription"].exclude()
   #fct["getFieldDesc"].call_policies = return_internal_reference()
   ##fct["findFieldDescription"].call_policies = return_internal_reference()   
      
   # FieldContainerFactory and FieldContainerFactoryBase.
   fcf = fcf_temp.decl
   osggen.exposeSingleton(fcf,"FieldContainerFactory")
   fcf_base = osg["FieldContainerFactoryBase"]
   fcf_base.include()
   osggen.handleClassDefaults(fcf_base, True)
   finalize(fcf_base)

   #for c in [c.related_class for c in fcf_base[0].recursive_bases]:
   #   c.include()
   #   osggen.handleClassDefaults(c, False)

   timing.append(["base field types",time.time()])

   # Expose instantiations of OSG::SimplePool<T>.
   for d in [rtnp_temp.decl, sop_temp.decl]:
      osggen.exposeTemplateInst(d)
      d["create"].call_policies = return_internal_reference()

   #osggen.exposeTemplateInst(tbp_temp.decl)

   scp_temp_decl = scp_temp.decl
   osggen.exposeTemplateInst(scp_temp_decl)
   osggen.handleClassDefaults(scp_temp_decl, True)
   for c in scp_temp_decl.constructors():
      c.exclude()

   sp_temp_decl = sp_temp.decl
   osggen.exposeTemplateInst(sp_temp_decl)
   osggen.handleClassDefaults(sp_temp_decl, True)
   sp_temp_decl["editSFUpdateMode"].exclude()
   sp_temp_decl["getSFUpdateMode"].call_policies = return_internal_reference()
   for c in sp_temp_decl.constructors():
      c.exclude()

   sp_temp_decl.add_declaration_code("""
namespace
{

void destroyedCallback(bp::object callable, OSG::FieldContainer* fc,
                       unsigned long i)
{
   callable(fc, i);
}

void wrapAddDestroyedFunctor(OSG::StageParent* sp, bp::object func,
                             const std::string& createSymbol)
{
   sp->addDestroyedFunctor(boost::bind(destroyedCallback, func, _1, _2),
                           createSymbol);
}

}
""")
   sp_temp_decl["addDestroyedFunctor"].exclude()
   sp_temp_decl.add_registration_code("""
      def("addDestroyedFunctor", wrapAddDestroyedFunctor,
          (bp::arg("func"), bp::arg("createSymbol")))
   """)

   # Performance monitor
   pm = pm_temp.decl
   pm.include()
   # Expose the singleton.
   osggen.exposeSingleton(pm, "PerfMonitor")
   pm_base = osg["PerfMonitorBase"]
   pm_base.include()
   finalize(pm_base)
   #osggen.handleClassDefaults(bl_base, False)
   nsi = osg["NestedSampleInfo"]
   nsi.include()
   nsi['string_hash'].exclude()
   finalize(nsi)

   # ----------------- Base Math types --------------- #
   print "---------------------------------------------------"
   print "Base Math Type"
   
   # Expose classes   
   line = osg.class_("Line")    #.finalize()
   line.include()
   line.noncopyable = False
   finalize(line)
   
   # Expose the VecStorage classes
   for vst in settings.vec_storage_typeinfo:
      size = vst.size
      type_alias = vst.type_str
      cname = template_alias_db[vst.alias].declaration.name       # Look up true class name by alias
      c = osg.class_(cname)
      #c.noncopyable = False
      c.include()
      c.rename(vst.exposed_name)
      c["_uiSize"].exclude()
      wrap_method(c, "getSeparateValues", 
                  "&osgwrap::VecStorage%s_getSeparateValues_tuple<%s>" % (size, type_alias))
      #osggen.addStrOperator(c)
      exclude_protected(c)

   # Expose the SelectVecStorage classes. These are included to get the Python class hierarchy to
   # match the C++ class hierarchy.
   for svst in settings.select_vec_storage_typeinfo:
      size = svst.size
      type_alias = svst.type_str
      cname = template_alias_db[svst.alias].declaration.name      # Look up true class name by alias
      c = osg.class_(cname)
      c.include()
      c.rename(svst.exposed_name)
      exclude_protected(c)

   # VecXX classes
   for vt in settings.vec_typeinfo:      
      c = template_alias_db[vt.alias].declaration.type.declaration
      c.noncopyable = False
      c.include()
      c.rename(vt.exposed_name)
      c["Null"].exclude()
      c["subZero"].call_policies = return_internal_reference()
      c["addToZero"].call_policies = return_internal_reference()
      add_member_function(c,"__setitem__","osgwrap::setArrayElement<%s,%s::ValueType>"%(vt.type,vt.type))
      osggen.addStrOperator(c)
      exclude_protected(c)
   
   # PntXX classes
   for pt in settings.pnt_typeinfo:
      #type_name = template_alias_db[pt.alias].declaration.name
      c = template_alias_db[pt.alias].declaration.type.declaration
      c.include()
      c.noncopyable = False
      #c = osg.class_(cname)
      c.rename(pt.exposed_name)
      c["Null"].exclude()
      c["subZero"][0].call_policies = return_internal_reference()
      #c["addToZero"][0].call_policies = return_internal_reference()
      wrap_method(c,"getValues", "osgwrap::getValues%d<%s>"%(pt.size,pt.type))
      add_member_function(c,"__setitem__","osgwrap::setArrayElement<%s,%s::ValueType>"%(pt.type,pt.type))
      osggen.addStrOperator(c)
      exclude_protected(c)
   
   # ColorXX classes
   for ct in settings.color_typeinfo:
      (size,type_abrv) = (ct.size,ct.type_str)
      c = template_alias_db[ct.alias].declaration.type.declaration
      c.include()
      c.rename(ct.exposed_name)
      if 3 == size:
         wrap_method(c,"getValuesRGB", "Color3_getValuesRGB_tuple<%s>"%type_abrv)
      else:         
         wrap_method(c,"getValuesRGBA", "Color4_getValuesRGBA_tuple<%s>"%type_abrv)
      wrap_method(c,"getValuesHSV", "Color_getValuesHSV< %s >"%ct.type)
      c.decls("convertToHSV",allow_empty=True).exclude()
      add_member_function(c,"__setitem__","osgwrap::setArrayElement<%s,%s::ValueType>"%(ct.type,ct.type))
      osggen.addStrOperator(c)
      exclude_protected(c)
      c.noncopyable = False

      
   # Matrix
   #c = template_alias_db["alias_Matrix"].declaration.type.declaration
   c = mat_temp.decl
   c.noncopyable = False
   c.include()
   finalize(c)

   # Remove the operator that returns a vector by const reference because it
   # results in returning a copy.
   for array_op in c['operator[]']:
      if is_const_ref(array_op.return_type):
         array_op.exclude()

   # Does not work for some reason. So we do it manually.
   #c.member_function(name = 'setValue',
   #                  arg_types = [pd.pointer_t(pd.const_t(pd.float_t())), pd.bool_t()]).exclude()

   for svm in c['setValue']:
      if 2 == len(svm.arguments):
         arg0 = svm.arguments[0]
         if pd.is_pointer(arg0._type) and pd.is_floating_point(pd.remove_pointer(arg0._type)):
            svm.exclude()
   c.add_registration_code('def("setValue", &osgwrap::setValueMatrix<float>, ( bp::arg("pMat"), bp::arg("bTransposed")=(bool)(true) ) )')

   wrap_method(c,"getValues", 'osgwrap::getValuesMatrix<float>')     
   wrap_method(c,"getTransform", 'osgwrap::getTransform_Matrix<float>')

   # Expose all mult and multLeft variations.
   matrix_value_types = ['OSG::Real32', 'OSG::Real64']
   for right_type in matrix_value_types:
      add_member_function(c, "mult", "osgwrap::wrapMatrixMult<OSG::Real32, %s>" % right_type)
      add_member_function(c, "multLeft", "osgwrap::wrapMatrixMultLeft<OSG::Real32, %s>" % right_type)

   c["jacobi"].exclude()
   osggen.addStrOperator(c)
   exclude_protected(c)

   # Expose Matrix4d
   c = matd_temp.decl
   c.noncopyable = False
   c.include()
   finalize(c)

   # Remove the operator that returns a vector by const reference because it
   # results in returning a copy.
   for array_op in c['operator[]']:
      if is_const_ref(array_op.return_type):
         array_op.exclude()

   # Does not work for some reason. So we do it manually.
   #c.member_function(name='setValue', arg_types = [pd.pointer_t(pd.const_t(pd.float_t())), pd.bool_t()]).exclude()

   for svm in c['setValue']:
      if 2 == len(svm.arguments):
         arg0 = svm.arguments[0]
         if pd.is_pointer(arg0._type) and pd.is_floating_point(pd.remove_pointer(arg0._type)):
            svm.exclude()
   c.add_registration_code('def("setValue", &osgwrap::setValueMatrix<double>, ( bp::arg("pMat"), bp::arg("bTransposed")=(bool)(true) ) )')

   wrap_method(c,"getValues", 'osgwrap::getValuesMatrix<double>')
   wrap_method(c,"getTransform", 'osgwrap::getTransform_Matrix<double>')

   # Expose all mult and multLeft variations.
   matrix_value_types = ['OSG::Real32', 'OSG::Real64']
   for right_type in matrix_value_types:
      add_member_function(c, "mult", "osgwrap::wrapMatrixMult<OSG::Real64, %s>" % right_type)
      add_member_function(c, "multLeft", "osgwrap::wrapMatrixMultLeft<OSG::Real64, %s>" % right_type)

   c["jacobi"].exclude()
   osggen.addStrOperator(c)
   exclude_protected(c)

   # Quaternion
   c = quat_temp.decl
   c.noncopyable = False
   c.include()
   finalize(c)
   c["_uiSize"].exclude()
   wrap_method(c,"getValues", 'osgwrap::getValues4<OSG::QuaternionBase<OSG::Real32> >')  
   wrap_method(c,"getValueAsQuat", 'osgwrap::getValues4<OSG::QuaternionBase<OSG::Real32> >')
   wrap_method(c,"getValueAsAxisDeg", 'osgwrap::getValueAsAxisDeg<OSG::Real32>')
   wrap_method(c,"getValueAsAxisRad", 'osgwrap::getValueAsAxisRad<OSG::Real32>')
   add_member_function(c,"__setitem__","osgwrap::setArrayElement<OSG::Quaternion, OSG::Quaternion::ValueType>")
   osggen.addStrOperator(c)
   exclude_protected(c)

   c = quatd_temp.decl
   c.noncopyable = False
   c.include()
   finalize(c)
   c["_uiSize"].exclude()
   wrap_method(c,"getValues", 'osgwrap::getValues4<OSG::QuaternionBase<OSG::Real64> >')  
   wrap_method(c,"getValueAsQuat", 'osgwrap::getValues4<OSG::QuaternionBase<OSG::Real64> >')
   wrap_method(c,"getValueAsAxisDeg", 'osgwrap::getValueAsAxisDeg<OSG::Real64>')
   wrap_method(c,"getValueAsAxisRad", 'osgwrap::getValueAsAxisRad<OSG::Real64>')
   add_member_function(c,"__setitem__","osgwrap::setArrayElement<OSG::Quaterniond, OSG::Quaterniond::ValueType>")
   osggen.addStrOperator(c)
   exclude_protected(c)
   
   # Plane
   c = osg.class_("Plane")
   c.include()
   c.noncopyable = False
   c["getNormal"].call_policies = return_internal_reference(1)
   # These transformations have to have the alias set explicitly or else Py++
   # will generate the "ugly" alias form as the name of the Python method.
   c["intersect"].add_transformation(FT.output(1), alias = "intersect")
   c["intersectInfinite"].add_transformation(FT.output(1),
                                             alias = "intersectInfinite")
   exclude_protected(c)

   for vol_name in ["Volume", "BoxVolume", "CylinderVolume", "FrustumVolume", "PolytopeVolume",
                    "SphereVolume"]:
      c = osg[vol_name][0]
      c.include()   
      osggen.handleClassDefaults(c, True)
      if "Volume" != vol_name:
         finalize(c)
      c.decls("getPlanes",allow_empty=True).exclude()
      exclude_protected(c)
   
   timing.append(["math types",time.time()])
      
   print "----------------[ Non-FC Classes ]-------------"
   
   # Standard classes
   for class_info in gen_classes:
      print "Class: %s"%class_info.name
   
      c = osg.class_(class_info.name)
      c.include()
      #base_c.finalize()
      osggen.handleClassDefaults(c, class_info.finalize)
      for x in class_info.excludes:            
         c[x].exclude()
      for (n,p) in class_info.policies:
         c[n].call_policies = p      
      if class_info.use_shared_ptr:
         c.held_type = "boost::shared_ptr<OSG::%s>"%class_info.name
      for d in class_info.declarations:
         c.add_declaration_code(d)
      for r in class_info.registrations:
         c.add_registration_code(r)
      if class_info.modCallable:         # Call any customizations needed
         class_info.modCallable(c)
      if class_info.finalize:
         finalize(c)

   action = osg.class_("Action")
   for aa in action['apply']:
      if 2 == len(aa.arguments):
         aa.exclude()

   # This defines a means to override the multi-node version of OSG::Action::apply(). The C++
   # signature is what is required for proper overriding, but the Python signature is what is
   # defined by wrapApply().
   action.add_wrapper_code("""
virtual OSG::ActionBase::ResultE apply(std::vector<OSG::Node*>::iterator begin,
                                       std::vector<OSG::Node*>::iterator end)
{
   // Transforms this apply() overload into something that works for Python.
   if ( bp::override func_apply = this->get_override("apply") )
   {
      // Create a list containing the nodes in the range identified by the
      // iterators.
      bp::list nodes;
      typedef std::vector<OSG::Node*>::iterator iter_type;
      for ( iter_type i = begin; i != end; ++i )
      {
         nodes.append(OSG::Node::ObjTransitPtr(*i));
      }

      // Hand the list off to Python.
      return func_apply(nodes);
   }
   else
   {
      return this->OSG::Action::apply(begin, end);
   }
}
    
OSG::ActionBase::ResultE default_apply(std::vector<OSG::Node*>::iterator begin,
                                       std::vector<OSG::Node*>::iterator end)
{
   return OSG::Action::apply(begin, end);
}
""")

   # We will keep the single-argument inherited overload of OSG::Action::apply(). For the
   # two-argument overload of OSG::Action::apply(), we exclude the regular virtual version. For
   # now, I do not see a need for a Python subclass to be able to customize OSG::IntersectAction
   # or OSG::RenderAction. If that changes, then something similar to what is done for the
   # two-argument OSG::Action::apply() overload can be applied to these classes.
   for class_name in ["IntersectAction", "RenderActionBase", "RenderAction"]:
      action = osg.class_(class_name)
      for member in [member for member in action.redefined_funcs() if 'apply' == member.name]:
         action.redefined_funcs().remove(member)

   c = sfh_temp.decl
   c.include()
   osggen.exposeSingleton(c,"SceneFileHandler")
   sfh_base = osg["SceneFileHandlerBase"]
   sfh_base.include()
   osggen.handleClassDefaults(sfh_base, True)
   finalize(sfh_base)
   for m in ["getPathHandler", "getDefaultGraphOp", "setReadCB", "getReadCB", "setWriteCB",
             "getWriteCB", "getReadProgressCB", "setReadProgressCB", "setWriteProgressCB",
             "getWriteProgressCB", "getGlobalResolver", "setGlobalResolver"]:
      sfh_base[m].exclude()
   # Need to do this because _defaultgraphOp is protected and can't be used as a default
   sfh_base.calldefs("read").exclude()           #use_default_arguments = False   
   sfh_base.add_declaration_code("""
namespace
{

OSG::NodeTransitPtr sfh_custom_read(OSG::SceneFileHandlerBase& sfh,
                                    const std::string& fileName)
{
   return sfh.read(fileName.c_str());
}

bp::list wrapGetSuffixList(OSG::SceneFileHandlerBase& sfh,
                           const OSG::UInt32 flags)
{
   std::list<const OSG::Char8*> list;
   sfh.getSuffixList(list, flags);
   bp::list python_list;

   typedef std::list<const OSG::Char8*>::iterator iter_type;
   for ( iter_type i = list.begin(); i != list.end(); ++i )
   {
      python_list.append(*i);
   }

   return python_list;
}

OSG::FieldContainer* globalResolverCallback(bp::object callable,
                                            const OSG::Char8* s)
{
   OSG::FieldContainer::ObjRecPtr fc =
      bp::extract<OSG::FieldContainer::ObjRecPtr>(callable(s));
   return fc.get();
}
/*
bp::object wrapGetGlobalResolver(const OSG::SceneFileHandlerBase& sfh)
{
   OSG::SceneFileType::Resolver res(sfh.getGlobalResolver());
   return bp::make_function(res, bp::return_value_policy<bp::reference_existing_object>());
}
*/
void wrapSetGlobalResolver(OSG::SceneFileHandlerBase* sfh,
                           bp::object callable)
{
   sfh->setGlobalResolver(boost::bind(globalResolverCallback, callable, _1));
}

}
""")
   add_member_function(sfh_base, "read", "sfh_custom_read")
   add_member_function(sfh_base, "setGlobalResolver", "wrapSetGlobalResolver")
   sfh_base["getSuffixList"].exclude()
   sfh_base.add_registration_code("""
      def("getSuffixList", wrapGetSuffixList,
          (bp::arg("flags") = OSG::SceneFileType::OSG_READ_SUPPORTED |
                              OSG::SceneFileType::OSG_WRITE_SUPPORTED))
   """)

   # Include ImageFileType
   ift = osg["ImageFileType"]
   ift.include()
   ift.add_declaration_code("""
namespace
{

bp::list wrapGetSuffixList(OSG::ImageFileType& ift)
{
   std::list<std::string> list = ift.getSuffixList();
   bp::list python_list;

   typedef std::list<std::string>::iterator iter_type;
   for ( iter_type i = list.begin(); i != list.end(); ++i )
   {
      python_list.append(*i);
   }

   return python_list;
}

}
""")
   ift["getSuffixList"].exclude()
   ift.add_registration_code("""
      def("getSuffixList", wrapGetSuffixList)
   """)

   # ImageFileHandler
   c = ifh_temp.decl
   c.include()
   osggen.exposeSingleton(c,"ImageFileHandler")
   ifh_base = osg["ImageFileHandlerBase"]
   ifh_base.include()
   osggen.handleClassDefaults(ifh_base, True)
   finalize(ifh_base)
   for m in ["getPathHandler", "setReadCB", "getReadCB", "store"]:
      ifh_base[m].exclude()
   # Need to do this because _defaultgraphOp is protected and can't be used as a default
   #ifh_base.calldefs("read").exclude()           #use_default_arguments = False   
   #ifh_base.calldefs("readTopNodes").exclude()   #use_default_arguments = False   
   ifh_base.add_declaration_code("""
namespace
{

bp::list wrapGetSuffixList(OSG::ImageFileHandlerBase& ifh,
                           const OSG::UInt32 flags)
{
   std::list<const OSG::Char8*> list;
   ifh.getSuffixList(list, flags);
   bp::list python_list;

   typedef std::list<const OSG::Char8*>::iterator iter_type;
   for ( iter_type i = list.begin(); i != list.end(); ++i )
   {
      python_list.append(*i);
   }

   return python_list;
}

bp::dict wrapGetMimeTypeMap(OSG::ImageFileHandlerBase& ifh)
{
   bp::dict python_dict;

   const OSG::ImageFileHandlerBase::TypeMap& mime_type_map = ifh.getMimeTypeMap();
   typedef OSG::ImageFileHandlerBase::TypeMap::const_iterator iter_type;
   for (iter_type itr = mime_type_map.begin(); itr != mime_type_map.end(); itr++)
   {
      python_dict[(*itr).first] = bp::ptr((*itr).second);
   }

   return python_dict;
}

bp::dict wrapGetSuffixTypeMap(OSG::ImageFileHandlerBase& ifh)
{
   bp::dict python_dict;

   const OSG::ImageFileHandlerBase::TypeMap& mime_type_map = ifh.getSuffixTypeMap();
   typedef OSG::ImageFileHandlerBase::TypeMap::const_iterator iter_type;
   for (iter_type itr = mime_type_map.begin(); itr != mime_type_map.end(); itr++)
   {
      python_dict[(*itr).first] = bp::ptr((*itr).second);
   }

   return python_dict;
}

}
""")
   #add_member_function(ifh_base, "read", "ifh_custom_read")
   ifh_base["getSuffixList"].exclude()
   ifh_base.add_registration_code("""
      def("getSuffixList", wrapGetSuffixList,
          (bp::arg("flags") = OSG::ImageFileType::OSG_READ_SUPPORTED |
                              OSG::ImageFileType::OSG_WRITE_SUPPORTED))
   """)
   ifh_base["getMimeTypeMap"].exclude()
   ifh_base.add_registration_code('def("getMimeTypeMap", wrapGetMimeTypeMap)')
   ifh_base["getSuffixTypeMap"].exclude()
   ifh_base.add_registration_code('def("getSuffixTypeMap", wrapGetSuffixTypeMap)')

   # OSG::ConnectionFactory
   c = cf_temp.decl
   c.include()
   osggen.exposeSingleton(c, "ConnectionFactory")
   cf_base = osg["ConnectionFactoryBase"]
   cf_base.include()
   # OSG::ConnectionFactoryBase::dump() is declared but not defined.
   cf_base["dump"].exclude()
   osggen.handleClassDefaults(cf_base, True)
   finalize(cf_base)

   # Expose stat classes
   sedb = osg["StatElemDescBase"]
   sedb.include()
   osggen.handleClassDefaults(sedb, False)
   sedb["findDescByName"].exclude()
   #finalize(sedb)
   
   for t in stat_templates:
      c = t.decl
      c.include()
      finalize(c)

   ebdh = osg.class_("ExceptionBinaryDataHandler")

   for f in ebdh.member_functions("getValue"):
      arg_type = remove_cv(remove_reference(f.argument_types[0]))
      if type(arg_type) == pd.cpptypes.bool_t:
         f.add_transformation(FT.output(0), alias = "getBool")
      elif type(arg_type) == pd.cpptypes.signed_char_t:
         f.add_transformation(FT.output(0), alias = "getInt8")
      elif type(arg_type) == pd.cpptypes.short_int_t:
         f.add_transformation(FT.output(0), alias = "getInt16")
      elif type(arg_type) == pd.cpptypes.int_t:
         f.add_transformation(FT.output(0), alias = "getInt32")
      elif arg_type.decl_string == 'OSG::Int64':
         f.add_transformation(FT.output(0), alias = "getInt64")
      elif type(arg_type) == pd.cpptypes.float_t:
         f.add_transformation(FT.output(0), alias = "getReal32")
      elif type(arg_type) == pd.cpptypes.double_t:
         f.add_transformation(FT.output(0), alias = "getReal64")
      elif type(arg_type) == pd.cpptypes.long_double_t:
         f.add_transformation(FT.output(0), alias = "getReal128")
      elif (arg_type.decl_string == 'std::string' or
            arg_type.decl_string == '::std::string'):
         f.add_transformation(FT.output(0), alias = "getString")
      else:
         #print "Excluding return type", arg_type.decl_string
         f.exclude()

   for f in ebdh.member_functions("putValue"):
      arg_type = remove_cv(remove_reference(f.argument_types[0]))
      if type(arg_type) == pd.cpptypes.bool_t:
         f.alias = "putBool"
      elif type(arg_type) == pd.cpptypes.signed_char_t:
         f.alias = "putInt8"
      elif type(arg_type) == pd.cpptypes.short_int_t:
         f.alias = "putInt16"
      elif type(arg_type) == pd.cpptypes.int_t:
         f.alias = "putInt32"
      elif arg_type.decl_string == 'OSG::Int64':
         f.alias = "putInt64"
      elif type(arg_type) == pd.cpptypes.float_t:
         f.alias = "putReal32"
      elif type(arg_type) == pd.cpptypes.double_t:
         f.alias = "putReal64"
      elif type(arg_type) == pd.cpptypes.long_double_t:
         f.alias = "putReal128"
      elif (arg_type.decl_string == 'std::string' or
            arg_type.decl_string == '::std::string'):
         f.alias = "putString"
      else:
         #print "Excluding data type", arg_type.decl_string
         f.exclude()

   # Graph ops   
   for n in ["GraphOp", "MakeTransparentGraphOp", "MaterialMergeGraphOp",
             "PruneGraphOp", "SharePtrGraphOp", "VerifyGeoGraphOp",
             "GeoTypeGraphOp", "StripeGraphOp", "SplitGraphOp",
             "VerifyGraphOp", "MergeGraphOp"]:
      op = osg.class_(n)
      op.include()
      # OSG::GraphOp subclasses are not intended to be instantiated directly by user-level code.
      op.no_init = True
      osggen.handleClassDefaults(op, False)
      op.decls("traverseEnter", allow_empty = True).include()
      op.decls("traverseLeave", allow_empty = True).include()

      if "GraphOp" != n:
         op.add_registration_code(
            "bp::implicitly_convertible<OSG::%s::ObjRefPtr, OSG::GraphOp::ObjRefPtr>();" % n,
            works_on_instance = False
         )
         op.add_registration_code(
            "bp::implicitly_convertible<OSG::%s::ObjTransitPtr, OSG::GraphOp::ObjRefPtr>();" % n,
            works_on_instance = False
         )

      op.add_registration_code(
         "bp::implicitly_convertible<OSG::%s::ObjTransitPtr, OSG::%s::ObjRefPtr>();" % (n, n),
         works_on_instance = False
      )

      # The following may not be useful at all.
      mb.add_registration_code('bp::def("getCPtr", &pyopensg::ToCPtr<OSG::%s::ObjRefPtr>, ' % n + \
                               'bp::return_value_policy<bp::reference_existing_object>());')

   c = osg.class_("MakeTransparentGraphOp").class_("MaterialObject")
   c["getMaterial"].call_policies = return_value_policy(reference_existing_object)
   c = osg.class_("MaterialMergeGraphOp").class_("MaterialObject")
   c["getMaterial"].call_policies = return_value_policy(reference_existing_object)

   # ----------------- Free functions and basic capabilities --------------- #
   print "---------------------------------------------------"
   print "Misc stuff"

   # Base methods
   osg["osgInit"].exclude()     # Add wrapper below to code creator
   osg_exit = osg["osgExit"]
   osg_exit.include()

   #add_method(mb,"getRefCount","&osgwrap::ptrGetRefCount")
   #add_method(mb,"getContainerId","&osgwrap::ptrGetContainerId")   
   add_method(mb, "osgInit", "&osgwrap::osgInitWrapper")

   osg["traverse"].exclude()
   add_method(mb, "traverse", "&osgwrap::traverseWrapper1")
   add_method(mb, "traverse", "&osgwrap::traverseWrapper2")
   add_method(mb, "traverse", "&osgwrap::traverseWrapper3")
   add_method(mb, "traverse", "&osgwrap::traverseWrapper4")
   
   # Free functions.
   for m in free_funcs:
      #print "m: %s"%m
      funcs = osg.decls(m)
      funcs.include()
      for f in funcs:
         f.call_policies = osggen.FC_POLICY_RESOLVER(f)
         for mod_callable in free_func_mods.get(m, []):
            mod_callable(f)

   mat_util = ["MatrixOrthogonal", "MatrixFrustum", "MatrixPerspective",
               "MatrixStereoPerspective", "MatrixLookAt", "MatrixProjection"]
   for f in mat_util:
      decl = osg[f]
      decl.include()

   osg["drawVolume"].include()

   # OSG::GraphOpFactoryBase and OSG:GraphOpFactory
   c = osg["GraphOpFactoryBase"]
   c.include()
   # Exclude methods that return iterators.
   c["begin"].exclude()
   c["end"].exclude()
   c = gof_temp.decl
   c.include()
   osggen.exposeSingleton(c, "GraphOpFactory")

   timing.append(["non-fc types and free funcs",time.time()])

   # --- Exception Translation  --- #
   print "---------------------------------------------------"
   print "Exception Translations"

   # OpenSG exceptions to be translated into a Python exception type.
   exc_types = [("ClusterException", "IOError"),
                ("SocketException", "IOError"),
                ("ExceptionBinaryDataHandler::ReadError", "IOError"),
                ("ExceptionBinaryDataHandler::WriteError", "IOError"),
                ("Exception", "Exception")
               ]

   for t in exc_types:
      class_hierarchy = t[0].split('::')
      ex_type_name = class_hierarchy.pop()
      ns = osg
      for c in class_hierarchy:
         ns = ns.class_(c)
      ex_type = ns.class_(ex_type_name)
      ex_type.translate_exception_to_string('PyExc_%s' % t[1], 'exc.what()')
      ex_type.include()
      finalize(ex_type)

   timing.append(["exception translation", time.time()])

   # ----------- Generate code for all the Field Containers  --- #
   print "---------------------------------------------------"
   print "Field Containers"
   
   rc = osg.class_("ReflexiveContainer") 
   rc.include()
   rc["getClassType"].call_policies = return_value_policy(reference_existing_object)
   for decl in rc["getFieldDescription"]:
      decl.call_policies = return_internal_reference()
   for name in ["getContainerSize", "changed"]:
      rc[name].exclude()
   for decl in rc["getType"]:
      # Having both a const and a non-const version of this method is pointless in Python.
      if is_const_ref(decl.return_type):
         decl.exclude()
      else:
         decl.call_policies = return_internal_reference()

   rc.variables(allow_empty = True).exclude()
   finalize(rc)

   fc = osg.class_("FieldContainer") 
   fc.include()
   dont_expose = ["onCreate", "onCreateAspect", "onDestroy", "NextFieldMask", 'isNodeCore',
                  "editSField", "editMField", "getFieldFlags", "deregister", "shallowCopy",
                  "shallowCopyLocal", "createAspectCopy", "getContainerSize"]
   osggen.excludeMembers(fc, dont_expose)

   for decl in fc["getType"]:
      # Having both a const and a non-const version of this method is pointless in Python.
      if is_const_ref(decl.return_type):
         decl.exclude()
      # OSG::ReflexiveContainer::getType() returns OSG::ReflexiveContainerType& while
      # OSG::FieldContainer::getType() returns OSG::FieldContainerType&. The covariant return type
      # does not seem to be handled correctly by Py++ (we want the most derived version), so we do
      # it manually here.
      elif decl.return_type.decl_string.find("ReflexiveContainerType") != -1:
         decl.exclude()
      else:
         decl.call_policies = return_internal_reference()

   # getClassType() is a static method that returns a reference to an existing
   # object. Because it is a static method, it cannot use
   # return_internal_refernce because there is no custodian object.
   fc["getClassType"].call_policies = return_value_policy(reference_existing_object)

   fc.variables(allow_empty=True).exclude()
   finalize(fc)
   osggen.addFCPtrCode(fc, mb, "FieldContainer", None)   
   
   # For each field container class we have information about, do the following:
   # - Generate code for class and classBase
   for key, cinfo in fc_infos.iteritems():
      base_name = None
      print "Field container: %s" % cinfo.name

      # If requested, expose the base
      #if has_base:
      if cinfo.hasBase:
         try:
            base_name      = cinfo.name + "Base"
            base_c = osg.class_(base_name)
            base_c.include()
            finalize(base_c)
            osggen.handleClassDefaults(base_c, True)
            finalize(base_c, True)
         except Exception, ex:
            print "Exception when exposing base class [%s].  Skipping" % base_name
            print "  details: ", ex

         #osggen.removePureVirtuals(base_c)
         #for x in base_excludes:            
         #   base_c[x].exclude()
         #for (n,p) in base_policies:
         #   base_c[n].call_policies = p      
         print "Done with %s" % base_name
         
      # Expose the core class
      c = osg.class_(cinfo.name)
      c.include()
      osggen.handleClassDefaults(c, True)
      finalize(c, True)
      #c.documentation = cinfo.documentation
      #osggen.removePureVirtuals(c)
      
      for x in cinfo.excludes:
         #print "   exclude:", x
	 try:
            c[x].exclude()
         except Exception, ex:
            pass
            #print "Exception excluding: %s\n%s"%(x,str(ex))
      for (n,p) in cinfo.policies:
         #print "   set policy for:", n
         try:
            c[n].call_policies = p   
         except Exception, ex:
            pass
            #print "Exception setting policies on: %s\n%s"%(n,str(ex))
      for method_info in cinfo.addMethods:         
         add_member_function(c, method_info[0],method_info[1])

      # Handle template naming.
      # XXX: This is a hack for the one specific case where it is used right now.
      # It should be refactored to use some hints from cinfo to choose naming method.
      for mname in cinfo.templateMethods:
         print "  member:", mname
         for f in c.member_functions(mname):
            ret_type = f.return_type
            print "    ret type:", ret_type
            print "        dict:", ret_type.__dict__
            if not is_void(ret_type):
               if hasattr(ret_type, "declaration"):
                  ret_alias = ret_type.declaration.alias
               else:
                  ret_alias = ret_type.CPPNAME.replace(" ", "_")
               f.alias = "%s_%s" % (f.alias, ret_alias)
               print "    aliases: %s  %s" % (f.alias, ret_alias)
            else:
               f.alias = f.name
            f.name = f.demangled_name
            f.create_with_signature = True

      # Register all the pointer conversions that we need and sets held type
      osggen.addFCPtrCode(c, mb, cinfo.name, cinfo.parent)
      
      # Apply mods (modification methods)
      if core_mods.has_key(key):
         #print "Apply mods for: ", key
         for class_mod in core_mods[key][0]:
            class_mod(c)
         for base_mod in core_mods[key][1]:
            base_mod(base_c)

   # Customizations for field container classes.

   c = osg.class_("Node")
   c.add_declaration_code("""
namespace
{

bp::list getChildren(OSG::Node* node)
{
   bp::list children;

   OSG::UInt32 num_children(node->getNChildren());
   for ( OSG::UInt32 i = 0; i < num_children; ++i )
   {
      children.append(OSG::Node::ObjTransitPtr(node->getChild(i)));
   }

   return children;
}

}
""")
   c.add_registration_code('add_property("children", getChildren)')

   c = osg.class_("MultiCore")
   c.add_declaration_code("""
namespace
{

bp::list getCores(OSG::MultiCore* mc)
{
   bp::list cores;

   OSG::UInt32 num_cores(mc->getNCores());
   for ( OSG::UInt32 i = 0; i < num_cores; ++i )
   {
      cores.append(OSG::NodeCore::ObjTransitPtr(mc->getCores(i)));
   }

   return cores;
}

}
""")
   c.add_registration_code('add_property("cores", getCores)')

   # There are two forms of OSG::ChunkMaterial::find(). One returns an OSG::Int32, and the other
   # returns an OSG::StateChunk*. The former needs no return value policy while the latter most
   # certainly does.
   c = osg.class_("ChunkMaterial")
   for m in c["find"]:
      if is_pointer(m.return_type):
         m.call_policies = return_value_policy(reference_existing_object)

   # There are two forms of OSG::ChunkOverrideGroup::find(). One returns an OSG::Int32, and the
   # other returns an OSG::StateChunk*. The former needs no return value policy while the latter
   # most certainly does.
   c = osg.class_("ChunkOverrideGroup")
   for m in c["find"]:
      if is_pointer(m.return_type):
         m.call_policies = return_value_policy(reference_existing_object)

   # GCC-XML does not give PyGCCXML enough information to set the default argument correctly
   # for the use of OSG::State::AutoSlotRepalce as the default value of a method parameter of
   # type int. We have to help it out.
   auto_slot_replace_users = ["ChunkMaterial", "ChunkOverrideGroup",
                              "DynamicStateGeneratorStageData"]
   for cname in auto_slot_replace_users:
      c = osg.class_(cname)

      for mname in ["addChunk", "subChunk", "find"]:
         for decl in c.decls(mname, allow_empty = True):
            for arg in decl.arguments:
               if arg.default_value == "AutoSlotReplace":
                  # NOTE: Using a string here is the correct behavior. The value of the string is
                  # substituted in the generated code.
                  arg.default_value = "OSG::State::AutoSlotReplace"

   wrap_method(osg['Camera'], 'calcViewRay', 'osgwrap::calcViewRay')
   # The following ought to be the preferred way to handle transforming the
   # signature of OSG::Camera::calcViewRay(), but it does not work. This may
   # be because Py++ sees the const reference to an OSG::Viewport object, a
   # type that has a protected destructor, and sets up its own wrapper for
   # that method. In so doing, perhaps it is ignoring the transformation added
   # here. Or maybe this is just being done at the wrong point in this script.
#   calc_view_ray = osg['Camera']['calcViewRay']
#   calc_view_ray.add_transformation(FT.output(0))

   wrap_method(osg['Viewport'], 'getNormalizedCoordinates',
               'osgwrap::getNormalizedCoordinates')
   #vp = osg["Viewport"]
#   vp["getNormalizedCoordinates"].add_transformation(
#      FT.modify_type(0, declarations.remove_const), FT.output(0), FT.output(1),
#      alias = "getNormalizedCoordinates"
#   )

   # Do not hold the GIL while OSG::Window::render() is invoked. This will
   # allow other Python threads to run while the (relatively) slow rendering
   # process takes place.
   c = osg.class_("Window")
   c["render"].add_transformation(osggen.unlocked_native(), alias = "render")

   c = osg.class_("DrawableStatsAttachment")
   exclude_ops = ["+=", "-="]
   # XXX: There is probably a faster way to do this using a matcher or something.
   for member in c.get_exportable_members():
      if isinstance(member, operator_t) and member.symbol in exclude_ops:
         member.exclude()

   c.add_declaration_code("""
namespace
{

void dsa_iadd(OSG::DrawableStatsAttachment* self,
              OSG::DrawableStatsAttachment* other)
{
   *self += other;
}

void dsa_isub(OSG::DrawableStatsAttachment* self,
              OSG::DrawableStatsAttachment* other)
{
   *self -= other;
}

}
""")
   add_member_function(c, "__iadd__", "dsa_iadd")
   add_member_function(c, "__isub__", "dsa_isub")

   c = osg.class_("RenderCallbackStage")
   c["addPreRenderFunctor"].exclude()
   c["addPostRenderFunctor"].exclude()
   c["fillPreRenderStore"].exclude()
   c["fillPostRenderStore"].exclude()

   c.add_declaration_code("""
namespace
{

void renderCallback(bp::object callable, OSG::DrawEnv* drawEnv)
{
   callable(drawEnv);
}

void wrapAddPreRenderFunctor(OSG::RenderCallbackStage* self,
                             bp::object callable,
                             const std::string& createSymbol)
{
   self->addPreRenderFunctor(boost::bind(renderCallback, callable, _1),
                             createSymbol);
}

void wrapAddPostRenderFunctor(OSG::RenderCallbackStage* self,
                              bp::object callable,
                              const std::string& createSymbol)
{
   self->addPostRenderFunctor(boost::bind(renderCallback, callable, _1),
                              createSymbol);
}

inline void
fillFunctorStore(const bp::list& callables,
                 OSG::RenderCallbackStage::RenderFunctorStore& store)
{
   const int length = bp::extract<int>(callables.attr("__len__")());
   store.resize(length);

   for ( int i = 0; i < length; ++i )
   {
      store[i] = boost::bind(renderCallback, callables[i], _1);
   }
}

void wrapFillPreRenderStore(OSG::RenderCallbackStage* self, bp::list callables)
{
   OSG::RenderCallbackStage::RenderFunctorStore store;
   fillFunctorStore(callables, store);
   self->fillPreRenderStore(store);
}

void wrapFillPostRenderStore(OSG::RenderCallbackStage* self, bp::list callables)
{
   OSG::RenderCallbackStage::RenderFunctorStore store;
   fillFunctorStore(callables, store);
   self->fillPostRenderStore(store);
}

}
""")
   add_member_function(c, "addPreRenderFunctor", "wrapAddPreRenderFunctor")
   add_member_function(c, "addPostRenderFunctor", "wrapAddPostRenderFunctor")
   add_member_function(c, "fillPreRenderStore", "wrapFillPreRenderStore")
   add_member_function(c, "fillPostRenderStore", "wrapFillPostRenderStore")

   for class_name in ["ShaderProgram", "SimpleSHLChunk"]:
      c = osg[class_name]
      for m in ["addProceduralVariable", "updateProceduralVariable"]:
         c[m].exclude()

      c.add_declaration_code("""
namespace
{

void proceduralCallback(bp::object callable, OSG::DrawEnv* env, int i)
{
   callable(env, i);
}

bool
wrapAddProceduralVariable(
   OSG::%(class_name)s* self, const OSG::Char8* name, bp::object pFunc,
   OSG::UInt32 uiDependency = OSG::ShaderProcVariable::SHDObject
)
{
   return self->addProceduralVariable(name,
                                      boost::bind(proceduralCallback, pFunc,
                                                  _1, _2),
                                      uiDependency);
}

bool
wrapUpdateProceduralVariable(
   OSG::%(class_name)s* self, const OSG::Char8* name, bp::object pFunc,
   OSG::UInt32 uiDependency = OSG::ShaderProcVariable::SHDObject
)
{
   return self->updateProceduralVariable(name,
                                         boost::bind(proceduralCallback, pFunc,
                                                     _1, _2),
                                         uiDependency);
}

}
""" % {'class_name' : class_name})
      c.add_registration_code("""
         def("addProceduralVariable", wrapAddProceduralVariable,
             (bp::arg("name"), bp::arg("pFunc"),
              bp::arg("uiDependency") = OSG::ShaderProcVariable::SHDObject))
""")
      c.add_registration_code("""
         def("updateProceduralVariable", wrapUpdateProceduralVariable,
             (bp::arg("name"), bp::arg("pFunc"),
              bp::arg("uiDependency") = OSG::ShaderProcVariable::SHDObject))
""")

   for class_name, _ in shader_obj_types:
      c = osg[class_name]

      # Expose bool getUniformVariable<T>(const OSG::Char8*, T&) as
      # (bool, T) getUniformVariable_T(const OSG::Char8*). This does double-duty of defining a
      # Python-ready alias and transforming the signature so that the by-reference parameter is
      # returned as part of a tuple.
      for method in c["getUniformVariable"]:
         naked_type = remove_reference(method.arguments[1].type)
         if is_fundamental(naked_type):
            ret_type = naked_type.CPPNAME.replace(" ", "_")
         else:
            ret_type = naked_type.declaration.alias
         method.add_transformation(FT.output(1), alias = "getUniformVariable_%s" % ret_type)

   c = osg["ShaderProgramVariables"]
   for method in c["addProceduralVariable"]:
      if len(method.arguments) == 4:
         method.exclude()
         break

   c["updateProceduralVariable"].exclude()

   c.add_declaration_code("""
namespace
{

void proceduralCallback(bp::object callable, OSG::DrawEnv* env, int i)
{
   callable(env, i);
}

bool wrapAddProceduralVariable(OSG::ShaderProgramVariables* self,
                               const OSG::Char8* name, bp::object pFunc,
                               OSG::UInt32 uiDependency,
                               OSG::MFInt32* pProcVarLoc)
{
   return self->addProceduralVariable(name,
                                      boost::bind(proceduralCallback, pFunc,
                                                  _1, _2),
                                      uiDependency, pProcVarLoc);
}

bool wrapUpdateProceduralVariable(OSG::ShaderProgramVariables* self,
                                  const OSG::Char8* name, bp::object pFunc,
                                  OSG::UInt32 uiDependency)
{
   return self->updateProceduralVariable(name,
                                         boost::bind(proceduralCallback, pFunc,
                                                     _1, _2),
                                         uiDependency);
}

}
""")
   c.add_registration_code("""
      def("addProceduralVariable", wrapAddProceduralVariable,
          (bp::arg("name"), bp::arg("pFunc"), bp::arg("uiDependency"),
           bp::arg("pProcVarLoc")))
""")
   c.add_registration_code("""
      def("updateProceduralVariable", wrapUpdateProceduralVariable,
          (bp::arg("name"), bp::arg("pFunc"), bp::arg("uiDependency")))
""")

   c = osg["ShaderVariableFunctor"]
   c.add_declaration_code("""
namespace
{

void functorCallback(bp::object callable, OSG::DrawEnv* env, int i)
{
   callable(env, i);
}

void wrapSetFunctor(OSG::ShaderVariableFunctor* svf, bp::object pFunc)
{
   svf->setFunctor(boost::bind(functorCallback, pFunc, _1, _2));
}

}
""")
   c["setFunctor"].exclude()
   c.add_registration_code('def("setFunctor", wrapSetFunctor, (bp::arg("pFunc")))')

   c = osg["ShaderExecutableVarChunk"]
   # This method has no C++ implementation behind it.
   c["setShaderExecutableChunk"].exclude()

   c = osg.class_("CubeMapGenerator")
   c["initData"].call_policies = return_internal_reference()

   c = osg.class_("State")
   c.add_declaration_code("""
namespace
{

OSG::StateChunkRecPtr wrapGetChunk(OSG::State* state, OSG::UInt32 chunkId)
{
   return OSG::StateChunkRecPtr(
             OSG::StateChunk::ObjTransitPtr(state->getChunk(chunkId))
          );
}

}
""")
   c["getChunk"].exclude()
   c.add_registration_code('def("getChunk", wrapGetChunk, (bp::arg("chunkId")))')

   c = osg.class_("AttachmentContainer")
   c["findAttachment"].call_policies = return_internal_reference()

   c = osg.class_("DynamicStateGenerator")
   c.add_declaration_code("""
namespace
{

OSG::FrameBufferObjectRecPtr
wrapGetRenderTarget(OSG::DynamicStateGenerator* dsg)
{
   return OSG::FrameBufferObjectRecPtr(
             OSG::FrameBufferObject::ObjTransitPtr(dsg->getRenderTarget())
          );
}

}
""")
   c["getRenderTarget"].exclude()
   c.add_registration_code('def("getRenderTarget", wrapGetRenderTarget)')

   # OSG::ShadowStageData::ShadowMapElem
   c = osg["ShadowStageData"]["ShadowMapElem"]
   c["DepthShadowMap"].exclude()
   c["ColorShadowMap"].exclude()
   c.add_declaration_code("""
namespace
{

const OSG::UInt32 DepthShadowMap = OSG::ShadowStageData::ShadowMapElem::DepthShadowMap;
const OSG::UInt32 ColorShadowMap = OSG::ShadowStageData::ShadowMapElem::ColorShadowMap;

}
""")
   c.add_registration_code('def_readonly("DepthShadowMap", DepthShadowMap)')
   c.add_registration_code('def_readonly("ColorShadowMap", ColorShadowMap)')

   c = osg["ComplexSceneManager"]
   # OSG::ComplexSceneManager
   c = osg["ComplexSceneManager"]
   c["the"].call_policies = return_value_policy(reference_existing_object)
   c["init"].exclude()
   c["setMainloop"].exclude()
   c["findNamedComponent"].exclude()

   c.add_declaration_code("""
namespace
{

void mainLoopCallback(bp::object callable)
{
   callable();
}

bool wrapInit(OSG::ComplexSceneManager* csm, bp::list pythonArgv)
{
   const int argc(bp::extract<int>(pythonArgv.attr("__len__")()));
   std::vector<char*> argv(argc);

   for ( int i = 0; i < argc; ++i )
   {
      argv[i] = bp::extract<char*>(pythonArgv[i]);
   }

   return csm->init(argc, &argv[0]);
}

void wrapSetMainloop(OSG::ComplexSceneManager* csm, bp::object fMainLoop)
{
   csm->setMainloop(boost::bind(mainLoopCallback, fMainLoop));
}

OSG::FieldContainerRecPtr
wrapFindNamedComponent(OSG::ComplexSceneManager* csm,
                       const OSG::Char8* szName)
{
   return OSG::FieldContainerTransitPtr(csm->findNamedComponent(szName));
}

}
""")

   c.add_registration_code('def("init", wrapInit)')
   c.add_registration_code('def("setMainloop", wrapSetMainloop, (bp::arg("fMainLoop")))')
   c.add_registration_code("""
      def("findNamedComponent", wrapFindNamedComponent, (bp::arg("szName")))
   """)

   for class_name in ["CSMClusterWindow", "CSMDrawManager", "CSMDrawer", "CSMWindow"]:
      c = osg[class_name]
      c["findNamedComponent"].exclude()

      c.add_declaration_code("""
namespace
{

OSG::FieldContainerRecPtr
wrapFindNamedComponent(OSG::%(class_name)s* self, const OSG::Char8* szName)
{
   return OSG::FieldContainerRecPtr(self->findNamedComponent(szName));
}

}
""" % { 'class_name' : class_name })
      c.add_registration_code("""
         def("findNamedComponent", wrapFindNamedComponent, (bp::arg("szName")))
      """)

   # Expose the derived geo prop classes (there are a *lot* of them)
   print "----- Geo Props -------"
   # Helper method
   def expose_geo_prop(temp, base_name):      
      c = temp.decl
      c.include()
      osggen.handleClassDefaults(c, True)
      finalize(c)
      osggen.addFCPtrCode(c, mb, c.name, base_name)   # Register the fcptr conversions

      # Exclude some methods that are difficult or impossible to wrap well
      for p in ["editFieldPtr","getFieldPtr","editField"]:
         c.decls(p,allow_empty=True).exclude()

      # Just use the base class version of these methods
      for n in ["getDimension","getNormalize","clear","resize","size","setValue","push_back","addValue"]:
	 c.decls(n,allow_empty=True).exclude()

   for temp in geo_vec_prop_templates:
      expose_geo_prop(temp, "OSG::GeoVectorProperty")
   for temp in geo_int_prop_templates:
      expose_geo_prop(temp, "OSG::GeoIntegralProperty")


   # Places that take an OSG::NullFC as a default value
   for x in ["createMipmap","scale","mirror", "reformat","scaleNextPower2","slice","subImage"]:
      osg["Image"][x].use_overload_macro = True   
   
   timing.append(["field containers",time.time()])
   
   # ------ Standard ------------- #
   # - Remove all op=
   # - Always expose with scope method
   all_classes = osg.classes()
   for c in all_classes:
      c.operators(symbol="=", allow_empty=True).exclude()
      c.always_expose_using_scope = True 

   timing.append(["standard",time.time()])
   
   print "Generating module..."
   
   #Now it is the time to give a name to our module
   mb.build_code_creator(module_name='_osg2')
   timing.append(["build creators",time.time()])
   
   mb.code_creator.add_namespace_usage("std")
   mb.code_creator.license = """
// PyOpenSG is (C) Copyright 2005-2009 by Allen Bierbaum
//
// This file is part of PyOpenSG.
//
// PyOpenSG is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2 of the License, or (at your option)
// any later version.
//
// PyOpenSG is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
// more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
   
   #for h in extra_includes:
   #   mb.code_creator.add_include(h)
      
   # Replace the auto generated list of headers
   #mb.code_creator.replace_included_headers( all_headers + extra_includes)
   #mb.code_creator.replace_included_headers( [main_header_filename] + extra_includes)
   mb.code_creator.replace_included_headers( extra_includes)
   
   if False:
      decl_file = file("decl_out.txt",'w', 1024000)
      print "Writing out decls to file... ",
      mb.print_declarations(osg, writer=decl_file.write)
      #mb.print_declarations(my_class)
      decl_file.close()
      print "done."

   # Create a code creator for our precompiled header and ensure it comes first.
   precompiled_header = pyplusplus.code_creators.include_t(main_header_filename)
   mb.code_creator.adopt_creator( precompiled_header, 0 )

   # So we don't have absolute paths in code
   mb.code_creator.user_defined_directories.append(opensg_inc_dir)
   
   print "Splitting module..."
   mb.split_module(pj(output_dir,'generated'))
   timing.append(["write module",time.time()])

def parseOptions(opensgIncDir):
   parser = optparse.OptionParser()
   parser.add_option("-s", "--osgsrc", dest = "opensg_src_dir",
                     type = "string", default = opensgIncDir,
                     help = "Path to OpenSG's Source directory")
   parser.add_option("-b", "--boostinc", dest = "boost_inc_dir",
                     type = "string",
                     help = "Path to Boost header parent directory")
   return parser.parse_args()

if use_profiler:
   import hotshot
   profiler = hotshot.Profile('gen_bindings.prof')
   profiler.runcall(main)
   profiler.close()
else:
   main()

print "Code generation complete.  took: %s"%(time.time()-timing[0][1])
for i in range(1,len(timing)):
   print "  %s: %s"%(timing[i][0], (timing[i][1]-timing[i-1][1]))
